
#include <stdio.h>
#include <stdlib.h>
#include <getopt.h>
#include <string.h>


/*
 * Funcion auxiliar.
 * Devuelve un char* con el resultado de concatenar los dos char* pasados por parametro
 */
char* agregar(char* original, char* agregado)
{
	char *viejo = original;
	char *nuevo = malloc(strlen(original)+strlen(agregado)+1);
	strcpy(nuevo, original);
	strcat(nuevo, agregado);
	free(viejo);
	return nuevo;
}

/*
 * Funcion auxiliar.
 * Devuelve un char* a partir de un char
 */
char* charToString(char caracter)
{
	char* string = malloc(2);
	string[0] = caracter;
	string[1] = '\0';
	return string;
}

/*
 * Funcion auxiliar.
 * Devuelve el el valor de un int en un char*.
 * Tomado de http://opensource.apple.com/source/groff/groff-10/groff/libgroff/itoa.c
 * Copyright (C) 1989, 1990, 1991, 1992 Free Software Foundation, Inc.
 */
#define INT_DIGITS 19

char *itoa(i)
     int i;
{
  /* Room for INT_DIGITS digits, - and '\0' */
  static char buf[INT_DIGITS + 2];
  char *p = buf + INT_DIGITS + 1;	/* points to terminating '\0' */
  if (i >= 0) {
    do {
      *--p = '0' + (i % 10);
      i /= 10;
    } while (i != 0);
    return p;
  }
  else {			/* i < 0 */
    do {
      *--p = '0' - (i % 10);
      i /= 10;
    } while (i != 0);
    *--p = '-';
  }
  return p;
}

void mostrarVersion()
{
    printf("tp0 (66.20 Organizacion de Computadoras)\n");
    printf("Copyright © 2011\n");
    printf("Versión 1.0\n");
}

void mostrarAyuda ()
{
    printf("Usage:\n\n");
    printf("tp0 [options] from-file to-file\n");
    printf("tp0 - find differences between two files\n\n");
    printf("Options:\n");
    printf("  -v, --version      Print version and quit.\n");
    printf("  -h, --help         Print this information and quit.\n");
    printf("  -o, --output       Specifies the output file.\n");
    printf("  -m, --max          Shows the first m differences.\n");
}

/*
 * Si no existe alguno de los archivos, se manda un error por stderr y se corta la ejecucion.
 */
void verificarArchivos(char* nombreArchivo1, FILE* fpArchivoEntrada1, char* nombreArchivo2, FILE* fpArchivoEntrada2)
{
	int exitoFile1 = 1;
	int exitoFile2 = 1;

	if ((fpArchivoEntrada1 == NULL)||(fpArchivoEntrada2 == NULL))
	{
		if(fpArchivoEntrada1 == NULL){
			fprintf(stderr,"tp0: No se pudo abrir el archivo %s\n", nombreArchivo1);
			exitoFile1 = 0;
		}

		if(fpArchivoEntrada2 == NULL){
			fprintf(stderr,"tp0: No se pudo abrir el archivo %s\n", nombreArchivo2);
			exitoFile2 = 0;
		}

		if (exitoFile1 == 1){
			fclose(fpArchivoEntrada1);
		}
		if (exitoFile2 == 1){
			fclose(fpArchivoEntrada2);
		}
		exit(1);
	}
}

/*
 * Verifica que la cantidad de diferencias encontradas hasta en momento no supere el limite de diferencias estamblecido
 * al ejecutar el programa con la opcion -m. Si la opcion es -o, limiteDeDiferencias es -1.
 */
int alcanzoLimiteDeDiferencias(int limiteDeDiferencias, int cantidadDeDiferencias)
{
	if (cantidadDeDiferencias == limiteDeDiferencias)
		return 0;
	return 1;
}

/*
 * Dadas dos lineas (una de cada archivo) se van recorriendo todos los caracteres buscando la diferencias. Se retorna un char* con
 * una linea para escribir en la salida que contiene el numero de la linea y los numeros de las columnas donde se encontraron las
 * diferencias.
 */
char* procesarDiferencias(int nroDeLinea, char* linea1, char* linea2, int limiteDeDiferencias, int* cantDiferenciasTotales)
{
	int longLinea1 = strlen(linea1);
	int longLinea2 = strlen(linea2);
	int nroColumna = 0;
	int cantDiferenciasLinea = 0;
	char* lineaDeDiferencias = malloc(strlen("")+1);
	strcpy(lineaDeDiferencias,"");

	/*Se recorren las dos lineas hasta que alguna termine o se supere el limite de diferencias requerido*/
	while((nroColumna < longLinea1)&&(nroColumna < longLinea2)&&(alcanzoLimiteDeDiferencias(limiteDeDiferencias,*cantDiferenciasTotales)==1))
	{
		if(linea1[nroColumna] != linea2[nroColumna])
		{
			if(cantDiferenciasLinea != 0)
				lineaDeDiferencias = agregar(lineaDeDiferencias, ", ");
			lineaDeDiferencias = agregar(lineaDeDiferencias, itoa(nroColumna));
			cantDiferenciasLinea++;
			*cantDiferenciasTotales = *cantDiferenciasTotales + 1;
		}
		nroColumna++;
	}

	/*si las lineas difieren totalmente*/
	if((cantDiferenciasLinea == longLinea1)||(cantDiferenciasLinea == longLinea2))
	{
		free(lineaDeDiferencias);
		if (longLinea1 > 0 || longLinea2 > 0){
			// Solo se reporta diferencia si alguna de las dos lineas tiene alguna caracter
			lineaDeDiferencias = malloc(strlen("line: ")+1);
			strcpy(lineaDeDiferencias, "line: ");
			lineaDeDiferencias = agregar(lineaDeDiferencias, itoa(nroDeLinea));
			lineaDeDiferencias = agregar(lineaDeDiferencias, " - Cols: all line.\n");
		} else {
			lineaDeDiferencias = malloc(strlen("")+1);
			strcpy(lineaDeDiferencias, "");
		}

	}else
	{
		/*si termino linea1 pero no linea2*/
		if ((cantDiferenciasLinea == longLinea1)||(cantDiferenciasLinea != longLinea2))
			while((nroColumna < longLinea2)&&(alcanzoLimiteDeDiferencias(limiteDeDiferencias,*cantDiferenciasTotales)==1))
			{
				if(cantDiferenciasLinea != 0)
					lineaDeDiferencias = agregar(lineaDeDiferencias, ", ");
				lineaDeDiferencias = agregar(lineaDeDiferencias, itoa(nroColumna));
				cantDiferenciasLinea++;
				*cantDiferenciasTotales = *cantDiferenciasTotales + 1;
				nroColumna++;
			}

		/*si termino linea2 pero no linea1*/
		if ((cantDiferenciasLinea != longLinea1)||(cantDiferenciasLinea == longLinea2))
			while((nroColumna < longLinea1)&&(alcanzoLimiteDeDiferencias(limiteDeDiferencias,*cantDiferenciasTotales)==1))
			{
				if(cantDiferenciasLinea != 0)
					lineaDeDiferencias = agregar(lineaDeDiferencias, ", ");
				lineaDeDiferencias = agregar(lineaDeDiferencias, itoa(nroColumna));
				cantDiferenciasLinea++;
				*cantDiferenciasTotales = *cantDiferenciasTotales + 1;
				nroColumna++;
			}

		if(cantDiferenciasLinea > 0)
		{
			char* cols = malloc(strlen(" - Cols: ")+1);
			strcpy(cols, " - Cols: ");
			char* line = malloc(strlen("line: ")+1);
			strcpy(line, "line: ");
			char* strNumLinea = malloc(strlen(itoa(nroDeLinea))+1);
			strcpy(strNumLinea, itoa(nroDeLinea));

			lineaDeDiferencias = agregar(cols, lineaDeDiferencias);
			lineaDeDiferencias = agregar(strNumLinea, lineaDeDiferencias);
			lineaDeDiferencias = agregar(line, lineaDeDiferencias);
			lineaDeDiferencias = agregar(lineaDeDiferencias, ".\n");
		}
	}

	return lineaDeDiferencias;
}


/*
 * Se recorren las lineas restantes del archivo que no se termino, marcando cada una como una diferencia all line
 */
void seguirConArchivoRestante(FILE* fp,char* caracter, int nroLinea, FILE* fpArchivoSalida, int limiteDeDiferencias, int* cantidadDeDiferencias)
{
	while ((*caracter!=EOF)&&(alcanzoLimiteDeDiferencias(limiteDeDiferencias,*cantidadDeDiferencias)==1))
	{
		char* lineaAImprimir = malloc(strlen("") + 1);
		strcpy(lineaAImprimir, "");
		while((*caracter != '\n')&&(alcanzoLimiteDeDiferencias(limiteDeDiferencias,*cantidadDeDiferencias)==1))
		{
			*caracter = fgetc(fp);
			*cantidadDeDiferencias = *cantidadDeDiferencias + 1;
		}
		lineaAImprimir = agregar(lineaAImprimir, "line: ");
		lineaAImprimir = agregar(lineaAImprimir, itoa(nroLinea));
		lineaAImprimir = agregar(lineaAImprimir, " - Cols: all line.\n");
		fputs(lineaAImprimir, fpArchivoSalida);
		free(lineaAImprimir);
		*caracter = fgetc(fp);
		nroLinea++;
	}
}

/*
 * Se procesan los dos archivos. Se arma una linea de cada una y se llama a procesarDiferencias
 */
void procesarArchivos(FILE* fpArchivoEntrada1, FILE* fpArchivoEntrada2, FILE* fpArchivoSalida, int limiteDeDiferencias)
{
	int cantidadDeDiferencias = 0;
	int nroLinea = 0;

	char caracterArchivo1 = fgetc(fpArchivoEntrada1);
	char caracterArchivo2 = fgetc(fpArchivoEntrada2);

	char* lineaArchivo1;
	char* lineaArchivo2;

	while ((caracterArchivo1!=EOF)&&(caracterArchivo2!=EOF)&&(alcanzoLimiteDeDiferencias(limiteDeDiferencias,cantidadDeDiferencias)==1))
	{
		/*Arma una linea del archivo1*/
		lineaArchivo1 = malloc(strlen("")+1);
		strcpy(lineaArchivo1,"");

		while(caracterArchivo1 != '\n')
		{
			char* strArch1 = charToString(caracterArchivo1);
			lineaArchivo1 = agregar(lineaArchivo1, strArch1);
			caracterArchivo1 = fgetc(fpArchivoEntrada1);
			free(strArch1);
		}

		/*Arma una linea del archivo2*/
		lineaArchivo2 =  malloc(strlen("")+1);
		strcpy(lineaArchivo2,"");

		while(caracterArchivo2 != '\n')
		{
			char* strArch2 = charToString(caracterArchivo2);
			lineaArchivo2 = agregar(lineaArchivo2, strArch2);
			caracterArchivo2 = fgetc(fpArchivoEntrada2);
			free(strArch2);
		}

		/*Se obtiene en lineaAImprimir las diferencias encontradas*/
		char* lineaAImprimir = procesarDiferencias(nroLinea, lineaArchivo1, lineaArchivo2, limiteDeDiferencias, &cantidadDeDiferencias);

		fputs(lineaAImprimir, fpArchivoSalida);
		free(lineaAImprimir);

		caracterArchivo1 = fgetc(fpArchivoEntrada1);
		caracterArchivo2 = fgetc(fpArchivoEntrada2);
		nroLinea++;
		free(lineaArchivo1);
		free(lineaArchivo2);
	}

	/*Si se termino el archivo1 pero no el archivo2*/
	if ((feof(fpArchivoEntrada1) != 0)&&(feof(fpArchivoEntrada2) == 0))
		seguirConArchivoRestante(fpArchivoEntrada2, &caracterArchivo2, nroLinea, fpArchivoSalida, limiteDeDiferencias, &cantidadDeDiferencias);

	/*Si se termino el archivo2 pero no el archivo1*/
	if ((feof(fpArchivoEntrada1) == 0)&&(feof(fpArchivoEntrada2) != 0))
		seguirConArchivoRestante(fpArchivoEntrada1, &caracterArchivo1, nroLinea, fpArchivoSalida, limiteDeDiferencias, &cantidadDeDiferencias);

	if(cantidadDeDiferencias<limiteDeDiferencias)
	{
		char* lineaAImprimir = malloc(strlen("tp0: Se encontraron ") + 1);
		strcpy(lineaAImprimir, "tp0: Se encontraron ");
		lineaAImprimir = agregar(lineaAImprimir, itoa(cantidadDeDiferencias));
		lineaAImprimir = agregar(lineaAImprimir, " diferencias. Numero menor a las ");
		lineaAImprimir = agregar(lineaAImprimir, itoa(limiteDeDiferencias));
		lineaAImprimir = agregar(lineaAImprimir, " solicitadas.\n");
		fputs(lineaAImprimir, fpArchivoSalida);
		free(lineaAImprimir);
	}
}

/***********************************************************************************************************/
int main (int argc, char* argv[]){

	const char* const opcionesCortas = "vho:m:";

	const struct option opcionesLargas[] =
	{
		{"version",    0, NULL, 'v'},
		{"help",       0, NULL, 'h'},
		{"output",     1, NULL, 'o'},
		{"max",        1, NULL, 'm'},
		{NULL,         0, NULL, 0}
	};

	char* archivoEntrada1;
	char* archivoEntrada2;
	char* archivoSalida;

	FILE* fpArchivoSalida = stdout;

	int limiteDeDiferencias = -1;
	int siguienteOpcion = 0;
	int cerrarArchivoSalida = 0;

	/*El siguiente bucle inicializa las variables de acuerdo a las opciones enviadas por consola*/
	while (siguienteOpcion != -1){

		siguienteOpcion = getopt_long(argc, argv, opcionesCortas, opcionesLargas, NULL);

		switch (siguienteOpcion){

		case 'h':
			mostrarAyuda();
			exit(0);

		case 'v':
			mostrarVersion();
			exit(0);

		case 'o':
			archivoSalida = optarg;
			fpArchivoSalida = fopen(archivoSalida,"w");
			if (fpArchivoSalida == NULL){
				fprintf(stderr, "tp0: No se puede crear o abrir el archivo de salida: %s\n", archivoSalida);
				exit (1);
			}
			cerrarArchivoSalida = 1;
			break;

		case 'm':
			limiteDeDiferencias = atoi(optarg);
			if (limiteDeDiferencias <= 0){
				fprintf(stderr, "tp0: Parametro invalido (valor negativo): -m %d\n", limiteDeDiferencias);
				exit (1);
			}
			break;
		}
	}

	/*Toma de parametros del programa*/
	int cantidadParametros = argc - optind; /*Contiene la cantidad de parametros que se enviaron*/

	if (cantidadParametros !=2){
		fprintf(stderr, "tp0: Cantidad incorrecta de parametros: %d. Deberian ser 2.\n", cantidadParametros);
		exit(1);
	}else{
		archivoEntrada1 = argv[optind];
		archivoEntrada2 = argv[optind+1];
	}

	FILE* fpArchivoEntrada1 = fopen(archivoEntrada1, "r");
	FILE* fpArchivoEntrada2 = fopen(archivoEntrada2, "r");

	verificarArchivos(archivoEntrada1, fpArchivoEntrada1, archivoEntrada2, fpArchivoEntrada2);

	procesarArchivos(fpArchivoEntrada1, fpArchivoEntrada2, fpArchivoSalida, limiteDeDiferencias);

	fclose(fpArchivoEntrada1);
	fclose(fpArchivoEntrada2);
	fpArchivoEntrada1 = NULL;
	fpArchivoEntrada2 = NULL;

	if (cerrarArchivoSalida == 1){
		fclose(fpArchivoSalida);
		fpArchivoSalida = NULL;
	}

	return 0;
}
